﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace JTLeigh.Tools.Development.CopySourceAsHtml
{

	/// <summary>
	/// Writes HTML to the clipboard.
	/// </summary>
    public static class ClipboardHtmlWriter
    {
        #region Inner Classes and Types

        private class ClipboardHtmlWriterState
        {
            public string Html { get; set; }
			public bool RestartVirtualMachineAdditions { get; set; }
            public bool Successful { get; set; }
        }

        #endregion // Inner Classes and Types

        #region Static Methods

		/// <summary>
		/// Writes the specified HTML to the clipboard.
		/// </summary>
		/// <param name="html">The HTML.</param>
		/// <param name="restartVirtualMachineAdditions"><see langword="true"/> if, after the
		/// first attempt to write to the clipboard fails, the VPC virtual machine additions
		/// should be restarted before subsequent attempts are made; <see langword="false"/>
		/// otherwise.
		/// </param>
		/// <returns><see langword="true"/> if the HTML was written successfully, <see langword="false"/>
		/// otherwise.</returns>
        public static bool Write(string html, bool restartVirtualMachineAdditions)
        {
            ClipboardHtmlWriterState state;
            Thread thread;

			state = new ClipboardHtmlWriterState
			{
				Html = html,
				RestartVirtualMachineAdditions = restartVirtualMachineAdditions,
			};

            thread = new Thread(Write);
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start(state);
            thread.Join();

            return state.Successful;
        }

        private static void Write(object obj)
        {
            ClipboardHtmlWriterState state;
            DataObject data;
			string path;
			Process newProcess;

            state = (ClipboardHtmlWriterState)obj;

            state.Successful = false;

            data = new DataObject();
            data.SetData(DataFormats.Text, true, state.Html);
            data.SetData(DataFormats.Html, true, Encode(state.Html));

			try
			{
				Clipboard.SetDataObject(data, true);
				state.Successful = true;
			}
			catch (ExternalException)
			{

				if (state.RestartVirtualMachineAdditions)
				{

					// restart the virtual machine additions and wait for them to finish starting up
					// before retrying.
					foreach (Process oldProcess in Process.GetProcessesByName("vmusrvc"))
					{
						path = oldProcess.MainModule.FileName;
						oldProcess.Kill();
						newProcess = Process.Start(new ProcessStartInfo(path)
						{
							CreateNoWindow = true,
							WindowStyle = ProcessWindowStyle.Hidden,
						});
						newProcess.WaitForInputIdle();
					}

					// set focus back to the main window.
					NativeMethods.SetForegroundWindow(Process.GetCurrentProcess().MainWindowHandle);

				}

				// retry several more times
				for (int i = 0; i < 4; i++) // 4 outer attempts
				{
					try
					{
						Clipboard.SetDataObject(data, true, 5, 100); // 5 inner attempts at 100ms intervals
						state.Successful = true;
						break;
					}
					catch (ExternalException)
					{
					}
				}

			}
        }

        private static string Encode(string html)
        {
            StringBuilder encodedHtml;
            int startHtml;
            int endHtml;
            int startFragment;
            int endFragment;

            encodedHtml = new StringBuilder();
            encodedHtml.Append("Version:0.9\r\n");
            encodedHtml.Append("StartHTML:aaaaaaaaaa\r\n");
            encodedHtml.Append("EndHTML:bbbbbbbbbb\r\n");
            encodedHtml.Append("StartFragment:cccccccccc\r\n");
            encodedHtml.Append("EndFragment:dddddddddd\r\n");
            startHtml = encodedHtml.Length;
            startFragment = encodedHtml.Length;
            encodedHtml.Append("<!--StartFragment-->\r\n");
            encodedHtml.Append(html);
            encodedHtml.Append("<!--EndFragment-->\r\n");
            endFragment = encodedHtml.Length;
            endHtml = encodedHtml.Length;
            encodedHtml.Replace("aaaaaaaaaa", startHtml.ToString("0000000000", CultureInfo.InvariantCulture), 0, startHtml);
			encodedHtml.Replace("bbbbbbbbbb", endHtml.ToString("0000000000", CultureInfo.InvariantCulture), 0, startHtml);
			encodedHtml.Replace("cccccccccc", startFragment.ToString("0000000000", CultureInfo.InvariantCulture), 0, startHtml);
			encodedHtml.Replace("dddddddddd", endFragment.ToString("0000000000", CultureInfo.InvariantCulture), 0, startHtml);
            return encodedHtml.ToString();
        }

        #endregion // Static Methods
    }

}
